// 判断当前是否为当前为commonjs环境
const isCommonjsEnvironment = () => typeof module !== "undefined" && typeof exports !== "undefined" && typeof require !== "undefined"

const getProcessVModelIdentifier = (() => {
    const fileMapName = "@@PLAIN_DESIGN_COMPOSITION_VMODEL_PROCESSOR"
    const importName = "processVModel"

    return (types, path, file) => {
        if (isCommonjsEnvironment()) {
            // 当前为commonjs环境
            let identifier = file.get(fileMapName)
            if (!identifier) {
                const programPath = path.find(p => p.isProgram());
                const scope = programPath.scope;
                identifier = scope.generateUidIdentifier(importName);
                file.set(fileMapName, identifier)
                const specifiers = [types.importSpecifier(identifier, types.identifier(importName))];
                let statement = types.importDeclaration(specifiers, types.stringLiteral('plain-design-composition'));
                programPath.unshiftContainer("body", [statement]);
            }
            return {...identifier}
        } else {
            if (typeof window !== "undefined") {
                // 浏览器下使用的时候，调用 window.PlainDesignComposition.processVModel
                return types.memberExpression(
                    types.memberExpression(
                        types.identifier('window'),
                        types.identifier('PlainDesignComposition'),
                    ),
                    types.identifier(importName)
                )
            } else {
                throw new Error('babel-plugin-react-model:无法识别当前环境,请确保在Node环境或者浏览器环境下运行！')
            }
        }
    }
})()

function genAssignmentCode(types, vModelValue, handlerParameter, path, file) {
    const object = vModelValue.object;
    const property = vModelValue.property;
    let propertyName = vModelValue.computed ? property : types.StringLiteral(property.name || property.value)
    return types.CallExpression(
        getProcessVModelIdentifier(types, path, file),
        [object, propertyName, handlerParameter]
    )
}

/**
 * v-change 适用于 ant-design 之类的组件；会编译成语法糖 value 以及 onChange；
 * 如果即使用了 v-change绑定某个属性，也显示地传递了onChange属性。那么 v-change的onChange与显示地传递的onChange会合并为一个函数。
 * 并且 v-change的onChange先执行；但是如果是在结构中传递的onChange，会导致某个onChange失效。如果v-model在结构对象之后，那么结构对象的onChange将无效；
 * 反之亦然。
 *
 * v-model 为专门用于 plain-design的组件。会编译成语法糖 value 以及 onUpdateModelValue。
 * 因为 plain-design的组件在派发时间 onUpdateModelValue的同时，也会派发onChange事件，所以没有v-change的问题。也不需要合并onChange属性。
 * 同时v-model支持绑定多个属性。比如 v-model-start 会编译成 start与 onUpdateStart，v-model-end 会编译成 end与onUpdateEnd
 *
 * @author  韦胜健
 * @date    2021/3/9 15:09
 */
const JSXModel = function (babel) {
    const {types} = babel;
    return {
        visitor: {
            Property: (path, file) => {
                const keyIdentifier = path.get('key')
                if (typeof keyIdentifier.node.value !== "string") {
                    return
                }
                if (keyIdentifier.node.value !== 'v-change' && keyIdentifier.node.value.indexOf('v-model') === -1) {
                    return;
                }
                if (keyIdentifier.node.value.indexOf('v-model') === 0) {
                    /*v-model 属性表达式*/
                    const sugarValueExpression = path.node.value;
                    if (!('property' in sugarValueExpression)) {throw new Error('v-model value must be a valid JavaScript member expression.')}
                    /*propName 绑定的字段名*/
                    let propName = keyIdentifier.node.value.slice(8)
                    if (!propName) {propName = 'modelValue'}
                    /*语法糖value属性值*/
                    const sugarValue = types.objectProperty(types.identifier(propName), sugarValueExpression);

                    /*语法糖change handler属性函数的第一个参数*/
                    const handlerFirstParameter = types.Identifier("$event");
                    /*语法糖change handler属性名*/
                    const handlerPropName = propName === 'modelValue' ? 'onUpdateModelValue' : 'onUpdate' + propName.charAt(0).toUpperCase() + propName.slice(1);
                    /*语法糖onChange属性值*/
                    const sugarOnChange = types.objectProperty(
                        types.identifier(handlerPropName),
                        types.ArrowFunctionExpression(
                            [handlerFirstParameter],
                            genAssignmentCode(types, sugarValueExpression, handlerFirstParameter, path, file)
                        )
                    );
                    path.replaceWithMultiple([...[
                        sugarValue,
                        sugarOnChange
                    ]]);
                }
                if (keyIdentifier.node.value.indexOf('v-change') === 0) {
                    /*v-model 属性表达式*/
                    const sugarValueExpression = path.node.value;
                    if (!('property' in sugarValueExpression)) {throw new Error('v-change value must be a valid JavaScript member expression.')}
                    /*propName 绑定的字段名*/
                    let propName = 'value'
                    /*语法糖value属性值*/
                    const sugarValue = types.objectProperty(types.identifier(propName), sugarValueExpression);

                    /*语法糖change handler属性函数的第一个参数*/
                    const handlerFirstParameter = types.Identifier("$event");
                    /*语法糖change handler属性名*/
                    const handlerPropName = 'onChange'
                    /*语法糖onChange属性值*/
                    const sugarOnChange = types.objectProperty(
                        types.identifier(handlerPropName),
                        types.ArrowFunctionExpression(
                            [handlerFirstParameter],
                            genAssignmentCode(types, sugarValueExpression, handlerFirstParameter, path, file)
                        )
                    );
                    path.replaceWithMultiple([...[
                        sugarValue,
                        sugarOnChange
                    ]]);
                }
            },
        }
    };
};

if (typeof window !== "undefined") {
    if ('Babel' in window) {
        Babel.registerPlugin("JSXModel", JSXModel);
    }
}

if (isCommonjsEnvironment()) {
    module.exports = JSXModel
}
